using System.Linq;
#region License and Terms
// MoreLINQ - Extensions to LINQ to Objects
// Copyright (c) 2008 Jonathan Skeet. All rights reserved.
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

namespace MoreLinq
{
    using System;
    using System.Collections;
    using System.Collections.Generic;

    public static partial class MoreEnumerable
    {
        /// <summary>
        /// Repeats the specific sequences <paramref name="count"/> times.
        /// </summary>
        /// <param name="sequence">The sequence to repeat</param>
        /// <param name="count">Number of times to repeat the sequence</param>
        /// <returns>A sequence produced from the repetition of the original source sequence</returns>
        
        public static IEnumerable<T> Repeat<T>(this IEnumerable<T> sequence, int count)
        {
            if (sequence == null) throw new ArgumentNullException("sequence");
            if (count < 0) throw new ArgumentOutOfRangeException("count", count, "Repeat count must be greater than or equal to zero.");
            switch(count)
            {
                case 0:
                    return Enumerable.Empty<T>();
                case 1:
                    return sequence;
                default:
                    return new RepeatEnumerable<T>(sequence, count);
            }
        }
        private class RepeatEnumerator<T> : IEnumerator<T>
        {
            private readonly BufferedEnumerable<T> _buffer;
            private IEnumerator<T> _enum;
            private int _duplicates;
            public RepeatEnumerator(IEnumerable<T> source, int count)
            {
                _enum = (_buffer = source.ToBuffer()).GetEnumerator();
                _duplicates = count - 1;
            }
            public T Current
            {
                get { return _enum.Current; }
            }
            object IEnumerator.Current
            {
                get { return _enum.Current; }
            }
            public void Dispose()
            {
                _enum.Dispose();
            }
            public bool MoveNext()
            {
                if(_enum.MoveNext())
                    return true;
                if(_duplicates != 0)
                {
                    --_duplicates;
                    _enum.Dispose();
                    _enum = _buffer.GetEnumerator();
                    return _enum.MoveNext(); //can only be false with zero-item sequence.
                }
                return false;
            }
            void IEnumerator.Reset()
            {
                throw new NotSupportedException();
            }
        }
        private class RepeatEnumerable<T> : IEnumerable<T>
        {
            private readonly IEnumerable<T> _source;
            private readonly int _count;
            public RepeatEnumerable(IEnumerable<T> source, int count)
            {
                _source = source;
                _count = count;
            }
            public IEnumerator<T> GetEnumerator()
            {
                return new RepeatEnumerator<T>(_source, _count);
            }
            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
        }
        private class RepeatIndefEnumerator<T> : IEnumerator<T>
        {
            private readonly BufferedEnumerable<T> _buffer;
            private IEnumerator<T> _enum;
            public RepeatIndefEnumerator(IEnumerable<T> source)
            {
                _enum = (_buffer = source.ToBuffer()).GetEnumerator();
            }
            public T Current
            {
                get { return _enum.Current; }
            }
            object IEnumerator.Current
            {
                get { return _enum.Current; }
            }
            public void Dispose()
            {
                _enum.Dispose();
            }
            public bool MoveNext()
            {
                if(!_enum.MoveNext())
                {
                    _enum.Dispose();
                    _enum = _buffer.GetEnumerator();
                    if(!_enum.MoveNext())
                        return false;//Can only happen with zero-item source.
                }
                return true;
            }
            void IEnumerator.Reset()
            {
                throw new NotSupportedException();
            }
        }
        private class RepeatIndefEnumerable<T> : IEnumerable<T>
        {
            private readonly IEnumerable<T> _source;
            public RepeatIndefEnumerable(IEnumerable<T> source)
            {
                _source = source;
            }
            public IEnumerator<T> GetEnumerator()
            {
                return new RepeatIndefEnumerator<T>(_source);
            }
            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
        }
        /// <summary>
        /// Repeats the specific sequences indefinitely.
        /// </summary>
        /// <param name="sequence">The sequence to repeat</param>
        /// <returns>A sequence produced from the indefinite repetition of the original source sequence</returns>
        public static IEnumerable<T> Repeat<T>(this IEnumerable<T> sequence)
        {
            if (sequence == null) throw new ArgumentNullException("sequence");
            return new RepeatIndefEnumerable<T>(sequence);
        }
    }
}